package xymt.novaway.system.controller.system;

import cn.dev33.satoken.annotation.SaCheckPermission;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xymt.novaway.common.core.utils.StringUtils;
import xymt.novaway.common.core.utils.file.FileUtils;
import xymt.novaway.common.oss.core.LocalOssClient;
import xymt.novaway.common.oss.exception.OssException;
import xymt.novaway.common.oss.factory.OssFactory;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.regex.Pattern;

/**
 * 本地文件访问控制器
 * 提供本地存储文件的HTTP访问接口
 *
 * @author NovaWay
 */
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/files")
public class LocalFileController {

    /**
     * 文件路径安全性校验正则表达式
     * 不允许包含 ../ .\ 等路径遍历字符
     */
    private static final Pattern UNSAFE_PATH_PATTERN = Pattern.compile(".*[/\\\\]\\.\\.([/\\\\].*)?");

    /**
     * 本地文件下载接口
     * 支持通过HTTP直接访问本地存储的文件
     *
     * @param request  HTTP请求对象，用于获取文件路径
     * @param response HTTP响应对象，用于输出文件内容
     */
    @SaCheckPermission("system:oss:download")
    @GetMapping("/**")
    public void downloadLocalFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
        try {
            // 获取文件相对路径
            String filePath = extractFilePathFromRequest(request);
            
            // 安全性验证
            validateFilePath(filePath);
            
            // 获取本地存储客户端
            LocalOssClient localClient = OssFactory.instanceLocal("local");
            
            // 设置响应头 - 根据文件扩展名确定Content-Type
            String fileName = getFileNameFromPath(filePath);
            String contentType = getContentType(fileName);
            response.setContentType(contentType);
            
            // 如果是下载模式，设置下载头
            if (isDownloadMode(request)) {
                FileUtils.setAttachmentResponseHeader(response, fileName);
            } else {
                // 内联显示模式，适用于图片、PDF等可直接显示的文件
                response.setHeader("Content-Disposition", "inline; filename=\"" + fileName + "\"");
            }
            
            // 使用LocalOssClient下载文件到响应流
            String fullUrl = "/api/files/" + filePath;
            localClient.download(fullUrl, response.getOutputStream(), response::setContentLengthLong);
            
            log.debug("本地文件访问成功: {}", filePath);
            
        } catch (Exception e) {
            log.error("本地文件访问失败: {}", e.getMessage(), e);
            handleFileAccessError(response, e);
        }
    }

    /**
     * 从HTTP请求中提取文件路径
     *
     * @param request HTTP请求对象
     * @return 文件相对路径
     */
    private String extractFilePathFromRequest(HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        String contextPath = request.getContextPath();
        
        // 移除上下文路径
        if (StringUtils.isNotEmpty(contextPath)) {
            requestURI = requestURI.substring(contextPath.length());
        }
        
        // 移除 /api/files/ 前缀
        if (requestURI.startsWith("/api/files/")) {
            return requestURI.substring("/api/files/".length());
        }
        
        throw new OssException("无效的文件路径请求: " + requestURI);
    }

    /**
     * 验证文件路径安全性
     *
     * @param filePath 文件路径
     */
    private void validateFilePath(String filePath) {
        if (StringUtils.isEmpty(filePath)) {
            throw new OssException("文件路径不能为空");
        }
        
        // 检查路径遍历攻击
        if (UNSAFE_PATH_PATTERN.matcher(filePath).matches()) {
            throw new OssException("检测到不安全的文件路径: " + filePath);
        }
        
        // 检查是否包含危险字符
        if (filePath.contains("..") || filePath.contains("~") || 
            filePath.startsWith("/") || filePath.contains("\\")) {
            throw new OssException("文件路径包含非法字符: " + filePath);
        }
        
        // 检查文件路径长度
        if (filePath.length() > 500) {
            throw new OssException("文件路径过长");
        }
    }

    /**
     * 从文件路径中提取文件名
     *
     * @param filePath 文件路径
     * @return 文件名
     */
    private String getFileNameFromPath(String filePath) {
        if (StringUtils.isEmpty(filePath)) {
            return "download";
        }
        
        int lastSlashIndex = filePath.lastIndexOf('/');
        if (lastSlashIndex >= 0 && lastSlashIndex < filePath.length() - 1) {
            return filePath.substring(lastSlashIndex + 1);
        }
        
        return filePath;
    }

    /**
     * 根据文件名获取Content-Type
     *
     * @param fileName 文件名
     * @return Content-Type
     */
    private String getContentType(String fileName) {
        if (StringUtils.isEmpty(fileName)) {
            return MediaType.APPLICATION_OCTET_STREAM_VALUE;
        }
        
        // 获取文件扩展名
        String extension = "";
        int lastDotIndex = fileName.lastIndexOf('.');
        if (lastDotIndex >= 0 && lastDotIndex < fileName.length() - 1) {
            extension = fileName.substring(lastDotIndex + 1).toLowerCase();
        }
        
        // 根据扩展名确定Content-Type
        return switch (extension) {
            case "jpg", "jpeg" -> "image/jpeg";
            case "png" -> "image/png";
            case "gif" -> "image/gif";
            case "webp" -> "image/webp";
            case "pdf" -> "application/pdf";
            case "txt" -> "text/plain; charset=utf-8";
            case "html", "htm" -> "text/html; charset=utf-8";
            case "css" -> "text/css; charset=utf-8";
            case "js" -> "application/javascript; charset=utf-8";
            case "json" -> "application/json; charset=utf-8";
            case "xml" -> "application/xml; charset=utf-8";
            case "mp4" -> "video/mp4";
            case "mp3" -> "audio/mpeg";
            case "zip" -> "application/zip";
            case "doc", "docx" -> "application/msword";
            case "xls", "xlsx" -> "application/vnd.ms-excel";
            case "ppt", "pptx" -> "application/vnd.ms-powerpoint";
            default -> MediaType.APPLICATION_OCTET_STREAM_VALUE;
        };
    }

    /**
     * 判断是否为下载模式
     * 通过查询参数 download=true 来确定
     *
     * @param request HTTP请求对象
     * @return 是否为下载模式
     */
    private boolean isDownloadMode(HttpServletRequest request) {
        String downloadParam = request.getParameter("download");
        return "true".equalsIgnoreCase(downloadParam) || "1".equals(downloadParam);
    }

    /**
     * 处理文件访问错误
     *
     * @param response HTTP响应对象
     * @param e        异常对象
     */
    private void handleFileAccessError(HttpServletResponse response, Exception e) throws IOException {
        if (e instanceof OssException) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
        } else if (e.getMessage() != null && e.getMessage().contains("文件不存在")) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在");
        } else {
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "服务器内部错误");
        }
    }

}